#ifndef PLUGIN_H
#define PLUGIN_H

/// @brief support class with methods needed to access plugins
/**
 *	@file Plugin.hpp support class containing methods of finding dynamic libraries external functions 
 *	and assigning them to pointers. Enables plugin registration.
 *
 *	@author Szymon Janikowski
 *	@author Pawel Gilewski
 */

#ifdef _WIN32
#include <Windows.h>
#else
#include <dlfcn.h>
#endif

#include <string>

namespace pluglib
{
	class Engine;

	/**
	 * @brief Class keeping the methods for dealing with plugin loading
	 * @note It has to be different on windows and linux system
	 */
	class Plugin
	{
	public:
		/**	@brief Constructor - main part of loading is done here
		 *	@note Given the filepath the plugin is found, 
		 *	search of registerFunction is performed and the result is assigned to pointer
		 *	if loaded contains true, that mean that object is ready for calling
		 *	registerPlugin
		 *	@param fileName path to plugin
		 *	@param loaded information about success
		 */
		Plugin(string fileName, bool &loaded)
		{
			loaded = true;
			#ifdef _WIN32

			//neccessary unicode convertions
			LPCWSTR fileNameInUnicode;
			std::wstring fileNameInWideString = s2ws(fileName);
			fileNameInUnicode = fileNameInWideString.c_str();
			
			//library loading
			DLLPointer_ = LoadLibrary(fileNameInUnicode);


			if (DLLPointer_)
			{
				try 
				{
					//checking for interface
					registerPluginFunctionPointer_ = reinterpret_cast<registerPluginFunction *>(::GetProcAddress(DLLPointer_, "registerPlugin"));
 
					if(! registerPluginFunctionPointer_)
					{
						loaded = false;
						::FreeLibrary(DLLPointer_);
					}
				}
				catch(...) 
				{
					::FreeLibrary(DLLPointer_);
				}
			}else loaded = false;
#else
			//loading library
			handle = dlopen(fileName.c_str(), RTLD_LAZY);
			if (handle) {
				try 
				{
					//checking for interface
					//typedef void (*registerPluginFunctionPtr)(Engine&);
					registerPluginFunctionPointer_ = reinterpret_cast<registerPluginFunction *>(dlsym(handle, "registerPlugin"));
 
					if(! registerPluginFunctionPointer_)
					{
						loaded = false;
						dlclose(handle);
					}
				}
				catch(...) 
				{
					loaded = false;
					dlclose(handle);
				}
			}else loaded = false;
#endif
		};

		~Plugin()
		{
#ifdef _WIN32
			::FreeLibrary(DLLPointer_);
#else
			dlclose(handle);
#endif
		};

		/** @brief Calling external function of the plugin 
		 *	@param engine reference to the singleton engine class - has to be passed, 
		 *	because normal singleton access doesn't work in external functions
		 */
		void registerPlugin(Engine &engine)
		{
			registerPluginFunctionPointer_(engine);
		}
	private:
		/// @brief Function required for unicode conversion
		std::wstring s2ws(const std::string& s)
		{
			std::wstring temp(s.length(),L' ');
			std::copy(s.begin(), s.end(), temp.begin());
			return temp;
		}

		/// @brief Type definition for external function pointer
		typedef void registerPluginFunction(Engine&);
#ifdef _WIN32
		HMODULE DLLPointer_;
#else
		void* handle;
#endif
		registerPluginFunction *registerPluginFunctionPointer_;
	};
};
#endif
